package redsig.hash;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;

import core.Adapter;


import data.XMLNode;

/**
 * Hasher class used to calculate the salt and hash values and apply them to the XMLNode objects
 * @author  Florian Bacher & Matthias Vavti
 * @version  1.0
 */
public class Hasher {
	
	/**
	 * @uml.property  name="rootNode"
	 * @uml.associationEnd  
	 */
	private XMLNode rootNode;
	private MessageDigest digest;
	
	public Hasher(){
		
	}
	
	/**
	 * 
	 * @param rootNode
	 * @throws NoSuchAlgorithmException
	 */
	public Hasher(XMLNode rootNode) throws NoSuchAlgorithmException{
		this.rootNode = rootNode;
		digest = MessageDigest.getInstance("SHA-1");
	}
	
	/**
	 * Wrapper method
	 */
	public void salt(){
		try {
			salt(rootNode, null);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Wrapper method
	 */
	public void salt(byte[] salt){
		try {
			salt(rootNode, salt);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Wrapper method
	 */
	public void hash(){
		try {
			hash(rootNode);
//			System.out.println("Hash-value = " +Adapter.byteToBase64(rootNode.getHashValue()));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 
	 * @param rootNode
	 * @throws NoSuchAlgorithmException
	 */
	private void salt(XMLNode rootNode, byte[] salt) throws NoSuchAlgorithmException{	
		if(rootNode == this.rootNode){
			//create initial salt for the root node if given salt is null
			if(salt == null){
				salt = new byte[20];
				//get an instance of the secure random generator
				SecureRandom random;
				random = SecureRandom.getInstance("SHA1PRNG");
				random.generateSeed(20);
				//create random value for the root
				random.nextBytes(salt);
				RandomTree rootSaltTree = new RandomTree(1, salt);
				ArrayList<byte[]> rootSalts = rootSaltTree.getSalts();
				rootNode.setSalt(rootSalts.get(0));
			}
			else{
				rootNode.setSalt(salt);
			}	
		}		
		RandomTree childSaltTree = new RandomTree(rootNode.getChildren().size(), rootNode.getSalt());
		ArrayList<byte[]> childSalts = childSaltTree.getSalts();
		int i = 0;
		for(XMLNode child : rootNode.getChildren()){
			if(child.getName().contains(Adapter.SALT_TAG)){	
				int index = child.getName().indexOf(Adapter.SALT_TAG);
				String salt2 = child.getName().substring(index + Adapter.SALT_TAG.length());
				String name = child.getName().substring(0, index);	
				child.setName(name);
				try {
					child.setSalt(Adapter.hexToByte(salt2));
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else{
					child.setSalt(childSalts.get(i));
			}
			i++;

			if(child.getChildren().isEmpty() == false)
				salt(child, child.getSalt());	
		}
	}
	
	/**
	 * 
	 * @param rootNode
	 * @return
	 * @throws IOException 
	 */
	private byte[] hash(XMLNode rootNode) throws IOException{
		MessageDigest digest;
		try {
			digest = MessageDigest.getInstance("SHA-1");
			if(rootNode.getChildren().isEmpty()){
				if(!(rootNode.isRedacted()) && !(rootNode.getName().contains(Adapter.REDACTED_TAG))){
					//digest.reset();
					digest.update(Adapter.base64ToByte(rootNode.getName()));
					rootNode.setHashValue(digest.digest(rootNode.getSalt()));
				}
				if(rootNode.getName().contains(Adapter.REDACTED_TAG)){
					int index = rootNode.getName().indexOf(Adapter.REDACTED_TAG);
					String hashValue = rootNode.getName().substring(index + Adapter.REDACTED_TAG.length());
					rootNode.setHashValue(Adapter.hexToByte(hashValue));
//					System.out.println("Redacted Node: " + Adapter.byteToHexString(rootNode.getHashValue()));
					rootNode.setName("*Redacted*");
					rootNode.setRedacted(true);
				}
			}		

			else {
				//digest.reset();
				digest.update(Adapter.base64ToByte(rootNode.getName()));
				for(XMLNode child : rootNode.getChildren()){
					digest.update(hash(child));	
				}
				rootNode.setHashValue(digest.digest());
			}
		
		return rootNode.getHashValue();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}


}
